package com.nbcio.iot.rule;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nbcio.iot.protocol.mqtt.MqttClientManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleBindings;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class RuleRuntimeService {

    private final MqttClientManager mqttClientManager;
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 最小可用：按 nodes/links 从入口节点开始顺序执行，仅支持 function、mqtt-out 两类动作
     */
    @SuppressWarnings("unchecked")
    public void process(Map<String, Object> msg, Map<String, Object> flowConfig) {
        try {
            List<Map<String, Object>> nodes = (List<Map<String, Object>>) flowConfig.get("nodes");
            List<Map<String, String>> links = (List<Map<String, String>>) flowConfig.get("links");
            if (nodes == null || links == null) return;

            // 简化：把第一个 inputs==0 的节点作为入口
            Map<String, Object> current = nodes.stream()
                .filter(n -> ((Number) n.getOrDefault("inputs", 0)).intValue() == 0)
                .findFirst().orElse(null);
            while (current != null) {
                String type = String.valueOf(current.get("type"));
                String id = String.valueOf(current.get("id"));
                log.info("[RULE] enter node={}, type={}", id, type);

                if ("function".equals(type)) {
                    Map<String, Object> cfg = (Map<String, Object>) current.get("config");
                    String code = cfg != null ? String.valueOf(cfg.getOrDefault("code", "return msg;")) : "return msg;";
                    msg = runJs(code, msg);
                } else if ("mqtt-out".equals(type)) {
                    Map<String, Object> cfg = (Map<String, Object>) current.get("config");
                    String topicTpl = cfg != null ? String.valueOf(cfg.getOrDefault("topic", "devices/${deviceCode}/data/out")) : "devices/${deviceCode}/data/out";
                    String topic = substitute(topicTpl, msg);
                    String payload = objectMapper.writeValueAsString(msg);
                    mqttClientManager.publish(topic, payload);
                    log.info("[RULE] mqtt-out topic={}, payload={}", topic, payload);
                } else if ("timer".equals(type)) {
                    // 定时器节点，不需要特殊处理，只是触发点
                    log.info("[RULE] timer node triggered: {}", id);
                    // 继续执行下一个节点
                }

                // 找到下一跳
                String currentId = String.valueOf(current.get("id"));
                String nextId = links.stream()
                    .filter(l -> currentId.equals(l.get("source")))
                    .map(l -> l.get("target"))
                    .findFirst().orElse(null);
                if (nextId == null) break;
                final String nid = nextId;
                current = nodes.stream().filter(n -> nid.equals(String.valueOf(n.get("id")))).findFirst().orElse(null);
            }
        } catch (Exception e) {
            log.warn("[RULE] process error: {}", e.getMessage());
        }
    }

    private Map<String, Object> runJs(String code, Map<String, Object> msg) throws Exception {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        if (engine == null) {
            // JDK17 无 Nashorn，可改用其它引擎；此处直接返回原消息
            return msg;
        }
        SimpleBindings bindings = new SimpleBindings();
        bindings.put("msg", msg);
        Object ret = engine.eval(code, bindings);
        if (ret instanceof Map) {
            return (Map<String, Object>) ret;
        }
        return msg;
    }

    private String substitute(String tpl, Map<String, Object> ctx) {
        String s = tpl;
        for (Map.Entry<String, Object> e : ctx.entrySet()) {
            s = s.replace("${" + e.getKey() + "}", String.valueOf(e.getValue()));
        }
        return s;
    }
} 